#include <algorithm>
#include <cctype>
#include <cstring>
#include <filesystem>
#include <list>
#include <map>
#include <queue>
#include <set>
#include <string>

namespace fs = std::filesystem;

// Custom comparator for path ordering
struct path_less
{
    bool
    operator()(fs::path const& a, fs::path const& b) const
    {
        std::string as = a.generic_string();
        std::string bs = b.generic_string();

        // Convert both strings to lowercase for case-insensitive comparison
        std::transform(as.begin(), as.end(), as.begin(), [](unsigned char c) { return std::tolower(c); });
        std::transform(bs.begin(), bs.end(), bs.begin(), [](unsigned char c) { return std::tolower(c); });

        return (as < bs) ? true : false;
    }
};

// Implementation of the CNApplication interface
class ChangeNamespace
{
  public:
    ChangeNamespace();
    ~ChangeNamespace() = default;

    // Utility functions for file type checking
    static bool is_source_file(fs::path const& p);
    static bool is_test_config_file(fs::path const& p);
    static bool is_manifest_json_file(fs::path const& p);
  
    // Set/get methods:

    void set_cppms_src_path(std::string_view p) ; // Set the path to the CppMicroServices source directory
    void set_destination(std::string_view p) ; // Set the destination path for modified files
    void set_namespace(std::string_view name) ; // Set the new namespace name
    void set_namespace_alias(bool) ; // Set whether to create an alias for the old namespace
    [[nodiscard]] std::string get_cppms_path() const ; // Get the CppMicroServices source path
    [[nodiscard]] std::string get_destination() const ; // Get the destination path
    [[nodiscard]] std::string get_namespace() const ; // Get the new namespace name

    // run: Main execution function for the namespace changing process
    // Checks if the CppMicroServices source and destination paths exist
    // Processes all files in the CppMicroServices directory
    // Handles pending paths and copies modified files
    int run() ;

    // Helper functions:

    // add_path: Adds the given file path to the list of paths to be copied
    // If the file is a source file, calls add_file_dependencies() to process its includes
    void add_path(fs::path const& p);

    // add_pending_path: Adds the given file to the list of pending paths to be parsed
    void
    add_pending_path(fs::path const& p)
    {
        m_pending_paths.push(p);
    }

    // add_directory: Iterates through all entries in the given directory
    // For each entry:
    //   - Removes the CppMicroServices path prefix
    //   - Adds the entry to the dependency tree
    //   - Adds the entry to the pending paths for future processing
    void add_directory(fs::path const& p);

    // add_file: Adds the given file path to the list of paths to be copied
    // If the file is a source file, calls add_file_dependencies() to process its includes
    void add_file(fs::path const& p);

    // copy_path: Copies a file from the source path to the destination path
    // If the file already exists in the destination, it's overwritten
    // For source files or test config files:
    //   - Reads the file content
    //   - Applies namespace replacements using regex
    //   - Adds namespace alias if required
    //   - Writes the modified content to the destination
    // For manifest JSON files:
    //   - Applies specific replacements for JSON content
    // For other files:
    //   - Performs a binary copy
    void copy_path(fs::path const& p);

  private:

    // Helper methods for replacing namespace and copying files to destination
    std::string read_file_content(fs::path const& file_path);
    void write_file_content(fs::path const& file_path, std::string const& fileContent);
    std::string replace_source_patterns(std::string const& fileContent);
    std::string add_namespace_alias(std::string const& fileContent);
    std::string replace_manifest_pattern(std::string const& fileContent);;

    // create_path: Recursively creates the directory structure for a given path
    // Ensures that all parent directories exist before creating a new directory
    void create_path(fs::path const& p);

    bool m_namespace_alias;                     // make "cppms" a namespace alias when doing a namespace rename.
    fs::path m_cppms_src_path;                  // the path to the cppms root
    fs::path m_dest_path;                       // the path to copy to
    std::set<fs::path, path_less> m_copy_paths; // list of files to copy
    std::map<fs::path, fs::path, path_less> m_dependencies;    // dependency information
    std::string m_namespace_name;                              // namespace rename.
    std::queue<fs::path, std::list<fs::path>> m_pending_paths; // Queue of paths we haven't scanned yet
};
